
#include <ctime>

#include "ILApp.h"

static CILApp* pApp;

#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480

LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC hdc;

    switch( message )
    {
    case WM_PAINT:
        hdc = BeginPaint( hWnd, &ps );
        EndPaint( hWnd, &ps );
        break;

    case WM_DESTROY:
        PostQuitMessage( 0 );
        break;
	case WM_LBUTTONDOWN:
	case WM_LBUTTONUP:
	case WM_MOUSEMOVE:
		pApp->HandleMouseMessage(message, wParam, lParam);
		break;
    default:
        return DefWindowProc( hWnd, message, wParam, lParam );
    }

    return 0;
}

HRESULT CILApp::init( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
	pApp = this;

    if( FAILED( InitWindow( hInstance, nShowCmd, WINDOW_WIDTH, WINDOW_HEIGHT ) ) )
        return E_FAIL;

    if( FAILED( InitD3D() ) )
    {
        CleanupD3D();
        return E_FAIL;
    }

    // some other init stuff here
    m_Width = WINDOW_WIDTH;
    m_Height = WINDOW_HEIGHT;
    m_Terrain.Init(m_pd3dDevice, IntPoint(200, 200), this);
    m_Camera.Init();
	pUnit = new CUnit(IntPoint(1,1), &m_Terrain, &m_Camera, m_pd3dDevice);
	pUnit->Goto(IntPoint(10,10));
    return S_OK;
}

HRESULT CILApp::InitWindow( HINSTANCE hInstance, int nShowCmd, int width, int height )
{
    // Register class
    WNDCLASSEX wcex;
    wcex.cbSize = sizeof( WNDCLASSEX );
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = 0;
    wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
    wcex.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = "TutorialWindowClass";
    wcex.hIconSm = 0;
    if( !RegisterClassEx( &wcex ) )
        return E_FAIL;

    // Create window
    m_WindowsHandles.hInst = hInstance;
    RECT rc = { 0, 0, width, height };
    AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
    m_WindowsHandles.hWnd = CreateWindow( "TutorialWindowClass", "Direct3D 11 Tutorial 2: Rendering a Triangle",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance,
        NULL );
    if( !m_WindowsHandles.hWnd )
        return E_FAIL;

    ShowWindow( m_WindowsHandles.hWnd, nShowCmd );
    
    m_time = clock();

    return S_OK;
}

int CILApp::Run()
{
    // Main message loop
    MSG msg = {0};
    while( WM_QUIT != msg.message )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            Render();
        }
    }

    return ( int )msg.wParam;
}

CCamera* CILApp::GetCamera()
{
    return &m_Camera;
}

void CILApp::Render()
{
    clock_t tStamp = clock();
    float dt=(tStamp-m_time)*(1.0f/CLOCKS_PER_SEC);
    m_time=tStamp;
    m_Camera.Update(dt);
	pUnit->Update(dt);
	ID3D11RenderTargetView* ppRTV[] = {m_pRenderTargetView, m_d3dHandles.pPickRTView};
    m_pd3dContext->OMSetRenderTargets( 2, ppRTV, m_d3dHandles.pDSV );

    RECT rc;
    GetClientRect( m_hWnd, &rc );
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;

    // Setup the viewport
    D3D11_VIEWPORT vp;
    vp.Width = (FLOAT)width;
    vp.Height = (FLOAT)height;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    m_pd3dContext->RSSetViewports( 1, &vp );
    float clrcol[4] = {1.0 , 1.00, 1.0, 1.0};
	UINT  iclrColor[4] = {-1, -1, -1 , -1};
    m_pd3dContext->ClearRenderTargetView(m_pRenderTargetView, clrcol);
	m_pD3DHelper->ClearRenderTargetViewUint(m_pd3dContext, m_d3dHandles.pPickRTView, iclrColor);
	m_pd3dContext->ClearDepthStencilView(m_d3dHandles.pDSV, D3D11_CLEAR_DEPTH, 1.0f, 0);

    m_Terrain.Render();
	pUnit->Render();
	
    m_d3dHandles.pSwapChain->Present(0, 0);
}

HRESULT CILApp::InitD3D()
{
    HRESULT hr = S_OK;

    RECT rc;
    GetClientRect( m_hWnd, &rc );
    m_Width = rc.right - rc.left;
    m_Height = rc.bottom - rc.top;

    UINT createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
    };
    UINT numFeatureLevels = ARRAYSIZE( featureLevels );

    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory( &sd, sizeof( sd ) );
    sd.BufferCount = 1;
    sd.BufferDesc.Width = m_Width;
    sd.BufferDesc.Height = m_Height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = m_hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;

    hr = D3D11CreateDeviceAndSwapChain( NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags, featureLevels, numFeatureLevels,
        D3D11_SDK_VERSION, &sd, &m_d3dHandles.pSwapChain, &m_pd3dDevice, &m_d3dHandles.featureLevel, &m_pd3dContext );

    if( FAILED( hr ) )
        return hr;

    // Create a render target view
    ID3D11Texture2D* pBackBuffer = NULL;
    hr = m_d3dHandles.pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&pBackBuffer );
    if( FAILED( hr ) )
        return hr;

    hr = m_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &m_pRenderTargetView );
    pBackBuffer->Release();
    if( FAILED( hr ) )
        return hr;

	hr = CD3DHelper::CreateDSBufferAndView(m_pd3dDevice, m_Width, m_Height, DXGI_FORMAT_D32_FLOAT, m_d3dHandles.pDS, m_d3dHandles.pDSV);

	if (FAILED(hr))
	{
		return hr;
	}
	hr = CreatePickingRenderTarget();
	
	m_pD3DHelper = new CD3DHelper(m_pd3dDevice);
    return S_OK;
}

void CILApp::CleanupD3D()
{
    if( m_pd3dContext ) m_pd3dContext->ClearState();

    if( m_pRenderTargetView ) m_pRenderTargetView->Release();
    if( m_d3dHandles.pSwapChain ) m_d3dHandles.pSwapChain->Release();
    if( m_pd3dContext ) m_pd3dContext->Release();
    if( m_pd3dDevice ) m_pd3dDevice->Release();
}

HRESULT CreateRenderTargetAndRTview(ID3D11Device* pDevice, UINT width, UINT height, DXGI_FORMAT format, ID3D11Texture2D** ppTex2d, ID3D11RenderTargetView** ppRTV)
{
	D3D11_TEXTURE2D_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.Width = width;
	desc.Height = height;
	desc.MipLevels = desc.ArraySize = 1;
	desc.Format = format;
	desc.SampleDesc.Count = 1;
	desc.Usage = D3D11_USAGE_DEFAULT;
	desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
	desc.CPUAccessFlags = 0;

	if (FAILED(pDevice->CreateTexture2D( &desc, NULL, ppTex2d )))
	{
		return E_FAIL;
	}

	return pDevice->CreateRenderTargetView(*ppTex2d, NULL, ppRTV);
}

HRESULT CILApp::CreatePickingRenderTarget()
{
	HRESULT hr = CreateRenderTargetAndRTview(m_pd3dDevice, m_Width, m_Height, DXGI_FORMAT_R32G32_UINT, &m_d3dHandles.pPickRT, &m_d3dHandles.pPickRTView);
	return hr;
}

void CILApp::HandleMouseMessage( UINT message, WPARAM wParam, LPARAM lParam )
{
	//TODO: use an IntPoint constructor for POINTS and transfer as IntPoint
	POINTS pos = MAKEPOINTS(lParam);
	int xPos = pos.x;
	int yPos = pos.y;

	switch (message)
	{
	case WM_LBUTTONDOWN:
		LeftMouseButtonDown(xPos, yPos);
		break;
	case WM_LBUTTONUP:
	case WM_MOUSEMOVE:
		MouseMove(xPos, yPos);
	}
}

void CILApp::LeftMouseButtonDown(int x, int y)
{
	int ObjectIndex = m_Mouse.SelectObject(m_pd3dDevice, x, y);
	m_Terrain.MarkTerrainVertex(ObjectIndex);
}

void CILApp::MouseMove( int xPos, int yPos )
{
	m_Mouse.Move(xPos, yPos);
}
